﻿#region << 版 本 注 释 >>
/*----------------------------------------------------------------
 * 创建者：huangyang
 * 电子邮箱：huangyang@tvscope.cn
 * 创建时间：2023/3/9 17:48:55
 * 版本：V1.0.0
 * 描述：
 *
 * ----------------------------------------------------------------
 * 修改人：
 * 时间：
 * 修改说明：
 *
 * 版本：V1.0.1
 *----------------------------------------------------------------*/
#endregion << 版 本 注 释 >>

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ImageK.Gui;
using ImageK.Measure;
using ImageK.Plugin.Frame;
using ImageK.Process;
using ImageK.Util;

namespace ImageK.Plugin
{
	/** This plugin implements the Image/Scale command. */
	public class Scaler : PlugIn {
	    private ImagePlus imp;
	    private static string xstr = "0.5";
	    private static string ystr = "0.5";
	    private string zstr = "1.0";
	    private static int newWidth, newHeight;
	    private int newDepth;
	    private bool doZScaling;
	    private static bool averageWhenDownsizing = true;
	    private static bool newWindow = true;
	    private static int staticInterpolationMethod = ImageProcessor.BILINEAR;
	    private int interpolationMethod = staticInterpolationMethod;
	    private string[] methods = ImageProcessor.getInterpolationMethods();
	    private static bool fillWithBackground;
	    private bool processStack = true;
	    private double xscale, yscale, zscale;
	    private string title = "Untitled";
	    private ArrayList fields;
	    private double bgValue;
	    private bool constainAspectRatio = true;
	    private TextBox xField, yField, zField, widthField, heightField, depthField;
	    private Rectangle r;
	    private object fieldWithFocus;
	    private int oldDepth;

	    public void run(string arg)
	    {
		    imp = IJ.getImage();
		    Roi roi = imp.getRoi();
		    ImageProcessor ip = imp.getProcessor();
		    if (roi != null && !roi.isArea())
			    ip.resetRoi();
		    if (!showDialog(ip))
			    return;
		    doZScaling = newDepth > 0 && newDepth != oldDepth;
		    if (doZScaling)
		    {
			    newWindow = true;
			    processStack = true;
		    }
		    if ((ip.getWidth() > 1 && ip.getHeight() > 1) || newWindow)
			    ip.setInterpolationMethod(interpolationMethod);
		    else
			    ip.setInterpolationMethod(ImageProcessor.NONE);
		    ip.setBackgroundValue(bgValue);
		    imp.startTiming();
		    try
		    {
			    if (newWindow && imp.getStackSize() > 1 && processStack)
			    {
				    ImagePlus imp2 = createNewStack(imp, ip, newWidth, newHeight, newDepth);
				    if (imp2 != null)
				    {
					    imp2.Show();
					    imp2.changes = true;
				    }
			    }
			    else
				    scale(imp);
		    }
		    catch (OutOfMemoryException o)
		    {
			    IJ.outOfMemory("Scale");
		    }
		    IJ.showProgress(1.0);
			//todo:
		    // record(imp, newWidth, newHeight, newDepth, interpolationMethod);
	    }

	    /** Returns a scaled copy of this image or ROI, where the
		     'options'  string can contain 'none', 'bilinear'. 'bicubic',
		    'slice' and 'constrain'.
	    */
	    public static ImagePlus resize(ImagePlus imp, int dstWidth, int dstHeight, int dstDepth, String options)
	    {
		    if (options == null)
			    options = "";
		    Scaler scaler = new Scaler();
		    if (options.Contains("none"))
			    scaler.interpolationMethod = ImageProcessor.NONE;
		    if (options.Contains("bicubic"))
			    scaler.interpolationMethod = ImageProcessor.BICUBIC;
		    if (scaler.xscale == 0)
		    {
			    scaler.xscale = (double)dstWidth / imp.getWidth();
			    scaler.yscale = (double)dstHeight / imp.getHeight();
			    scaler.zscale = (double)dstDepth / imp.getStackSize();
		    }
		    bool processStack = imp.getStackSize() > 1 && !options.Contains("slice");
		    //return new ImagePlus("Untitled", ip.resize(dstWidth, dstHeight, useAveraging));
		    Roi roi = imp.getRoi();
		    ImageProcessor ip = imp.getProcessor();
		    if (roi != null && !roi.isArea())
			    ip.resetRoi();
		    scaler.doZScaling = dstDepth != 1;
		    if (scaler.doZScaling)
			    scaler.processStack = true;
		    return scaler.createNewStack(imp, ip, dstWidth, dstHeight, dstDepth);
	    }

	    private ImagePlus createNewStack(ImagePlus imp, ImageProcessor ip, int newWidth, int newHeight, int newDepth)
	    {
		    int nSlices = imp.getStackSize();
		    int w = imp.getWidth(), h = imp.getHeight();
		    ImagePlus imp2 = imp.createImagePlus();
		    Rectangle r = ip.getRoi();
		    bool crop = r.Width != imp.getWidth() || r.Height != imp.getHeight();
		    ImageStack stack1 = imp.getStack();
		    ImageStack stack2 = new ImageStack(newWidth, newHeight);
		    bool virtualStack = stack1.isVirtual();
		    double min = imp.getDisplayRangeMin();
		    double max = imp.getDisplayRangeMax();
		    ImageProcessor ip1, ip2;
		    int method = interpolationMethod;
		    if (w == 1 || h == 1)
			    method = ImageProcessor.NONE;
		    for (int i = 1; i <= nSlices; i++)
		    {
			    IJ.ShowStatus("Scale: " + i + "/" + nSlices);
			    ip1 = stack1.getProcessor(i);
			    string label = stack1.getSliceLabel(i);
			    if (crop)
			    {
				    ip1.setRoi(r);
				    ip1 = ip1.crop();
			    }
			    ip1.setInterpolationMethod(method);
			    ip2 = ip1.resize(newWidth, newHeight, averageWhenDownsizing);
			    if (ip2 != null)
				    stack2.addSlice(label, ip2);
			    IJ.showProgress(i, nSlices);
		    }
		    imp2.setStack(title, stack2);
		    if (virtualStack)
			    imp2.setDisplayRange(min, max);
		    Calibration cal = imp2.getCalibration();
		    if (cal.scaled())
		    {
			    cal.pixelWidth *= 1.0 / xscale;
			    cal.pixelHeight *= 1.0 / yscale;
		    }
		    cal.xOrigin *= xscale;
		    cal.yOrigin *= yscale;
		    Overlay overlay = imp.getOverlay();
		    if (overlay != null && !imp.getHideOverlay() && !doZScaling)
		    {
			    //todo:
			    // overlay = overlay.duplicate();
			    // Rectangle roi = imp.getProcessor().getRoi();
			    // if (roi != null)
			    // 	overlay = overlay.crop(ip.getRoi());
			    // imp2.setOverlay(overlay.scale(xscale, yscale));
		    }
		    IJ.showProgress(1.0);
		    int[] dim = imp.getDimensions();
		    imp2.setDimensions(dim[2], dim[3], dim[4]);
		    if (imp.isComposite())
		    {
			    // imp2 = new CompositeImage(imp2, ((CompositeImage)imp).getMode());
			    // ((CompositeImage)imp2).copyLuts(imp);
		    }
		    if (imp.isHyperStack())
			    imp2.setOpenAsHyperStack(true);
		    if (doZScaling)
		    {
			    double oldSize = imp2.getStackSize();
			    Resizer resizer = new Resizer();
			    resizer.setAverageWhenDownsizing(averageWhenDownsizing);
			    imp2 = resizer.zScale(imp2, newDepth, interpolationMethod);
			    cal = imp2.getCalibration();
			    cal.zOrigin *= imp2.getStackSize() / oldSize;
		    }
		    return imp2;
	    }

	    private void scale(ImagePlus imp)
	    {
		    ImageProcessor ip = imp.getProcessor();
		    if (newWindow)
		    {
			    Rectangle r = ip.getRoi();
			    ImagePlus imp2 = imp.createImagePlus();
			    imp2.setProcessor(title, ip.resize(newWidth, newHeight, averageWhenDownsizing));
			    Calibration cal = imp2.getCalibration();
			    if (cal.scaled())
			    {
				    cal.pixelWidth *= 1.0 / xscale;
				    cal.pixelHeight *= 1.0 / yscale;
			    }
			    cal.xOrigin *= xscale;
			    cal.yOrigin *= yscale;
			    Overlay overlay = imp.getOverlay();
			    if (overlay != null && !imp.getHideOverlay())
			    {
				    // overlay = overlay.duplicate();
				    // int slice = imp.getCurrentSlice();
				    // overlay.crop(slice, slice);
				    // Rectangle roi = imp.getProcessor().getRoi();
				    // if (roi != null)
				    // 	overlay = overlay.crop(ip.getRoi());
				    // overlay = overlay.scale(xscale, yscale);
				    // imp2.setOverlay(overlay);
			    }
			    imp2.Show();
			    imp.trimProcessor();
			    imp2.trimProcessor();
			    imp2.changes = true;
		    }
		    else
		    {
			    if (processStack && imp.getStackSize() > 1)
			    {
				    Undo.reset();
				    StackProcessor sp = new StackProcessor(imp.getStack(), ip);
				    sp.scale(xscale, yscale, bgValue);
			    }
			    else
			    {
				    ip.snapshot();
				    Undo.setup(Undo.FILTER, imp);
				    ip.setSnapshotCopyMode(true);
				    ip.scale(xscale, yscale);
				    ip.setSnapshotCopyMode(false);
			    }
			    imp.deleteRoi();
			    imp.updateAndDraw();
			    imp.changes = true;
		    }
	    }

	    public static void record(ImagePlus imp, int w2, int h2, int d2, int method)
        {
            throw new NotImplementedException();
            // if (!Recorder.scriptMode())
            // 	return;
            // string options = "";
            // if (method == ImageProcessor.NONE)
            // 	options = "none";
            // else if (method == ImageProcessor.BICUBIC)
            // 	options = "bicubic";
            // else
            // 	options = "bilinear";
            // Recorder.recordCall("imp = imp.resize(" + w2 + ", " + h2 + (d2 > 0 && d2 != imp.getStackSize() ? ", " + d2 : "") + ", \"" + options + "\");");
        }

	    bool showDialog(ImageProcessor ip)
	    {
		    string options = Macro.GetOptions();
		    bool isMacro = options != null;
		    if (isMacro)
		    {
			    if (options.Contains(" interpolate"))
				    options = options.Replace(" interpolate", " interpolation=Bilinear");
			    else if (!options.Contains(" interpolation="))
				    options = options + " interpolation=None";
			    if (options.Contains("width=") && options.Contains(" height="))
			    {
				    xstr = "-";
				    ystr = "-";
				    if (options.Contains(" depth="))
					    zstr = "-";
				    else
					    zstr = "1.0";
			    }
			    Macro.setOptions(options);
			    interpolationMethod = ImageProcessor.BILINEAR;
		    }
		    int bitDepth = imp.getBitDepth();
		    int stackSize = imp.getStackSize();
		    bool isStack = stackSize > 1;
		    oldDepth = stackSize;
		    if (isStack && !isMacro)
		    {
			    xstr = "1.0";
			    ystr = "1.0";
			    zstr = "1.0";
		    }
		    r = ip.getRoi();
		    int width = newWidth;
		    if (width == 0) width = r.Width;
		    int height = (int)Math.Round(((double)width * r.Height / r.Width));
		    xscale = Tools.parseDouble(xstr, 0.0);
		    yscale = Tools.parseDouble(ystr, 0.0);
		    zscale = 1.0;
		    if (xscale != 0.0 && yscale != 0.0)
		    {
			    width = (int)Math.Round(r.Width * xscale);
			    height = (int)Math.Round(r.Height * yscale);
		    }
		    else
		    {
			    xstr = "-";
			    ystr = "-";
		    }
		    GenericDialog gd = new GenericDialog("Scale");
		    gd.addStringField("X Scale:", xstr);
		    gd.addStringField("Y Scale:", ystr);
		    if (isStack)
			    gd.addStringField("Z Scale:", zstr);
		    // gd.setInsets(5, 0, 5);
		    gd.addStringField("Width (pixels):", "" + width);
		    gd.addStringField("Height (pixels):", "" + height);
		    if (isStack)
		    {
			    string label = "Depth (images):";
			    if (imp.isHyperStack())
			    {
				    int slices = imp.getNSlices();
				    int frames = imp.getNFrames();
				    if (slices == 1 && frames > 1)
				    {
					    label = "Depth (frames):";
					    oldDepth = frames;
				    }
				    else
				    {
					    label = "Depth (slices):";
					    oldDepth = slices;
				    }
			    }
			    gd.addStringField(label, "" + oldDepth);
		    }
		    fields = gd.getStringFields();
		    if (fields != null)
		    {
			    for (int i = 0; i < fields.Count; i++)
			    {
				    // ((TextBox)fields[i]).addTextListener(this);
				    // ((TextBox)fields[i]).addFocusListener(this);
			    }
			    xField = (TextBox)fields[0];
			    yField = (TextBox)fields[1];
			    if (isStack)
			    {
				    zField = (TextBox)fields[2];
				    widthField = (TextBox)fields[3];
				    heightField = (TextBox)fields[4];
				    depthField = (TextBox)fields[5];
			    }
			    else
			    {
				    widthField = (TextBox)fields[2];
				    heightField = (TextBox)fields[3];
			    }
		    }
		    fieldWithFocus = xField;
		    gd.addChoice("Interpolation:", methods, methods[interpolationMethod]);
		    if (bitDepth == 8 || bitDepth == 24)
			    gd.addCheckbox("Fill with background color", fillWithBackground);
		    gd.addCheckbox("Average when downsizing", averageWhenDownsizing);
		    bool hyperstack = imp.isHyperStack() || imp.isComposite();
		    if (isStack && !hyperstack)
			    gd.addCheckbox("Process entire stack", processStack);
		    gd.addCheckbox("Create new window", newWindow);
		    title = WindowManager.getUniqueName(imp.getTitle());
		    // gd.setInsets(10, 0, 0);
		    gd.addStringField("Title:", title, 12);
		    gd.showDialog();
		    if (gd.wasCanceled())
			    return false;
		    xstr = gd.getNextString();
		    ystr = gd.getNextString();
		    xscale = Tools.parseDouble(xstr, 0.0);
		    yscale = Tools.parseDouble(ystr, 0.0);
		    if (isStack)
		    {
			    zstr = gd.getNextString();
			    zscale = Tools.parseDouble(zstr, 0.0);
		    }
		    string wstr = gd.getNextString();
		    newWidth = (int)Math.Round(Tools.parseDouble(wstr, 0));
		    newHeight = (int)Math.Round(Tools.parseDouble(gd.getNextString(), 0));
		    if (newHeight != 0 && (wstr.Equals("-") || wstr.Equals("0")))
			    newWidth = (int)Math.Round(newHeight * (double)r.Width / r.Height);
		    else if (newWidth != 0 && newHeight == 0)
			    newHeight = (int)Math.Round(newWidth * (double)r.Height / r.Width);
		    else if (newHeight != 0 && newWidth == 0)
			    newWidth = (int)Math.Round(newHeight * (double)r.Width / r.Height);
		    if (newWidth == 0 || newHeight == 0)
		    {
			    IJ.error("Scaler", "Width or height is 0");
			    return false;
		    }
		    if (xscale > 0.0 && yscale > 0.0)
		    {
			    newWidth = (int)Math.Round(r.Width * xscale);
			    newHeight = (int)Math.Round(r.Height * yscale);
		    }
		    if (isStack)
		    {
			    newDepth = (int)Math.Round(Tools.parseDouble(gd.getNextString(), 0));
			    if (zscale > 0.0)
			    {
				    int nSlices = stackSize;
				    if (imp.isHyperStack())
				    {
					    int slices = imp.getNSlices();
					    int frames = imp.getNFrames();
					    if (slices == 1 && frames > 1)
						    nSlices = frames;
					    else
						    nSlices = slices;
				    }
				    newDepth = (int)Math.Round(nSlices * zscale);
			    }
		    }
		    interpolationMethod = gd.getNextChoiceIndex();
		    if (bitDepth == 8 || bitDepth == 24)
			    fillWithBackground = gd.getNextBoolean();
		    averageWhenDownsizing = gd.getNextBoolean();
		    if (isStack && !hyperstack)
			    processStack = gd.getNextBoolean();
		    if (hyperstack)
			    processStack = true;
		    newWindow = gd.getNextBoolean();
		    if (xscale == 0.0)
		    {
			    xscale = (double)newWidth / r.Width;
			    yscale = (double)newHeight / r.Height;
		    }
		    gd.setSmartRecording(true);
		    title = gd.getNextString();
		    if (fillWithBackground)
		    {
			    Color bgc = ToolBar.getBackgroundColor();
				//todo:
			    // if (bitDepth == 8)
				   //  bgValue = ip.getBestIndex(bgc);
			    // else if (bitDepth == 24)
				   //  bgValue = bgc.getRGB();
		    }
		    else
			    bgValue = 0.0;
		    if (!isMacro)
			    staticInterpolationMethod = interpolationMethod;
		    return true;
	    }

	// public void textValueChanged(TextEvent e)
	// {
	// 	if (xField == null || yField == null)
	// 		return;
	// 	Object source = e.getSource();
	// 	double newXScale = xscale;
	// 	double newYScale = yscale;
	// 	double newZScale = zscale;
	// 	if (source == xField && fieldWithFocus == xField)
	// 	{
	// 		String newXText = xField.getText();
	// 		newXScale = Tools.parseDouble(newXText, 0);
	// 		if (newXScale == 0) return;
	// 		if (newXScale != xscale)
	// 		{
	// 			int newWidth = (int)Math.round(newXScale * r.width);
	// 			widthField.setText("" + newWidth);
	// 			if (constainAspectRatio)
	// 			{
	// 				yField.setText(newXText);
	// 				int newHeight = (int)Math.round(newXScale * r.height);
	// 				heightField.setText("" + newHeight);
	// 			}
	// 		}
	// 	}
	// 	else if (source == yField && fieldWithFocus == yField)
	// 	{
	// 		String newYText = yField.getText();
	// 		newYScale = Tools.parseDouble(newYText, 0);
	// 		if (newYScale == 0) return;
	// 		if (newYScale != yscale)
	// 		{
	// 			int newHeight = (int)Math.round(newYScale * r.height);
	// 			heightField.setText("" + newHeight);
	// 		}
	// 	}
	// 	else if (source == zField && fieldWithFocus == zField)
	// 	{
	// 		String newZText = zField.getText();
	// 		newZScale = Tools.parseDouble(newZText, 0);
	// 		if (newZScale == 0) return;
	// 		if (newZScale != zscale)
	// 		{
	// 			int nSlices = imp.getStackSize();
	// 			if (imp.isHyperStack())
	// 			{
	// 				int slices = imp.getNSlices();
	// 				int frames = imp.getNFrames();
	// 				if (slices == 1 && frames > 1)
	// 					nSlices = frames;
	// 				else
	// 					nSlices = slices;
	// 			}
	// 			int newDepth = (int)Math.round(newZScale * nSlices);
	// 			depthField.setText("" + newDepth);
	// 		}
	// 	}
	// 	else if (source == widthField && fieldWithFocus == widthField)
	// 	{
	// 		int newWidth = (int)Math.round(Tools.parseDouble(widthField.getText(), 0.0));
	// 		if (newWidth != 0)
	// 		{
	// 			int newHeight = (int)Math.round(newWidth * (double)r.height / r.width);
	// 			heightField.setText("" + newHeight);
	// 			xField.setText("-");
	// 			yField.setText("-");
	// 			newXScale = 0.0;
	// 			newYScale = 0.0;
	// 		}
	// 	}
	// 	else if (source == depthField && fieldWithFocus == depthField)
	// 	{
	// 		int newDepth = (int)Math.round(Tools.parseDouble(depthField.getText(), 0.0));
	// 		if (newDepth != 0)
	// 		{
	// 			zField.setText("-");
	// 			newZScale = 0.0;
	// 		}
	// 	}
	// 	xscale = newXScale;
	// 	yscale = newYScale;
	// 	zscale = newZScale;
	// }

	// public void focusGained(FocusEvent e)
	// {
	// 	fieldWithFocus = e.getSource();
	// 	if (fieldWithFocus == widthField)
	// 		constainAspectRatio = true;
	// 	else if (fieldWithFocus == yField)
	// 		constainAspectRatio = false;
	// }
	//
	// public void focusLost(FocusEvent e) { }

}

}
